#include "ParticleSystem.h"
#include "Effects.h"
#include "GraphCamera.h"
#include "RenderContext.h"
#include "vertex.h"
#include "level.h"
#include "InputState.h"
#include "GraphCamera.h"
#include <vector>

ParticleSystem::ParticleSystem(Type type, float3 position, GraphCamera* cam):LevelElement(),
	mType(type),
	mInitVB(0), 
	mDrawVB(0), 
	mStreamOutVB(0), 
	mTexArrayRV(0), 
	mRandomTexRV(0),
	mMaxParticles(500),
	m_GraphCam(cam)
{
	mFirstRun = true;
	mGameTime = 0.0f;
	mTimeStep = 0.0f;
	mAge      = 0.0f;

	mEyePosW  = D3DXVECTOR4(cam->GetPos(), 1.0f);
	mEmitPosW = D3DXVECTOR4(position.x, position.y, position.z, 1.0f);
	mEmitDirW = D3DXVECTOR4(0.0f, 1.0f, 0.0f, 0.0f);
}

ParticleSystem::~ParticleSystem()
{
	SafeRelease(mInitVB);
	SafeRelease(mDrawVB);
	SafeRelease(mStreamOutVB);
}

float ParticleSystem::GetAge()const
{
	return mAge;
}

void ParticleSystem::SetEyePos(const D3DXVECTOR3& eyePosW)
{
	mEyePosW = D3DXVECTOR4(eyePosW.x, eyePosW.y, eyePosW.z, 1.0f);
}

void ParticleSystem::SetEmitPos(const D3DXVECTOR3& emitPosW)
{
	mEmitPosW = D3DXVECTOR4(emitPosW.x, emitPosW.y, emitPosW.z, 1.0f);
}

void ParticleSystem::SetEmitDir(const D3DXVECTOR3& emitDirW)
{
	mEmitDirW = D3DXVECTOR4(emitDirW.x, emitDirW.y, emitDirW.z, 0.0f);
}

void ParticleSystem::Initialize(ContentManager *pContentManager)
{
	

	CreateEffect(pContentManager);
	DefineInputlayout();
	BuildVertexBuffer();

	vector<tstring> texnames;
	if(mType == Fire)
	{
		mMaxParticles = 500;
		texnames.push_back(L"./Textures/particles/fire/flare.dds"); 
		mTexArrayRV  = pContentManager->GetTextureArray(m_pLevel->GetDevice(), L"flares", texnames);
		
	}else if(mType == Rain)
	{
		mMaxParticles = 1000;
		texnames.push_back(L"./Textures/particles/rain/raindrop.dds"); 
		mTexArrayRV  = pContentManager->GetTextureArray(m_pLevel->GetDevice(), L"raindrops", texnames);
	}
	mRandomTexRV = pContentManager->GetRandomTexture(m_pLevel->GetDevice());
}

void ParticleSystem::Reset()
{
	mFirstRun = true;
	mAge      = 0.0f;
}

void ParticleSystem::Tick(const InputState & refInputState)
{
	mGameTime = refInputState.GetGameTime();
	mTimeStep = refInputState.GetDeltaTime();

	mAge += refInputState.GetDeltaTime();

	SetEyePos(m_GraphCam->GetPos());
	if(mType == Rain) SetEmitPos(m_GraphCam->GetPos());
	if(mAge > 3.0f) Reset();
}
void ParticleSystem::Draw(const RenderContext* pRenderContext, ID3D10ShaderResourceView* shadowdepthmap)
{
	D3DXMATRIX V = pRenderContext->GetCamera()->GetView();
	D3DXMATRIX P = pRenderContext->GetCamera()->GetProj();

	//
	// Set constants.
	//
	mfxViewProjVar->SetMatrix((float*)&(V*P));
	mfxGameTimeVar->SetFloat(mGameTime);
	mfxTimeStepVar->SetFloat(mTimeStep);
	mfxEyePosVar->SetFloatVector((float*)&mEyePosW);
	mfxEmitPosVar->SetFloatVector((float*)&mEmitPosW);
	mfxEmitDirVar->SetFloatVector((float*)&mEmitDirW);
	mfxTexArrayVar->SetResource(mTexArrayRV);
	mfxRandomTexVar->SetResource(mRandomTexRV);
	//
	// Set IA stage.
	//
	DefineInputlayout();

	m_pLevel->GetDevice()->IASetInputLayout(m_pVertexLayout);
    m_pLevel->GetDevice()->IASetPrimitiveTopology(D3D10_PRIMITIVE_TOPOLOGY_POINTLIST);

	UINT stride = sizeof(ParticleVertex);
    UINT offset = 0;

	// On the first pass, use the initialization VB.  Otherwise, use
	// the VB that contains the current particle list.
	if( mFirstRun )
		m_pLevel->GetDevice()->IASetVertexBuffers(0, 1, &mInitVB, &stride, &offset);
	else
		m_pLevel->GetDevice()->IASetVertexBuffers(0, 1, &mDrawVB, &stride, &offset);

	//
	// Draw the current particle list using stream-out only to update them.  
	// The updated vertices are streamed-out to the target VB. 
	//
	m_pLevel->GetDevice()->SOSetTargets(1, &mStreamOutVB, &offset);

    D3D10_TECHNIQUE_DESC techDesc;
    mStreamOutTech->GetDesc( &techDesc );
    for(UINT p = 0; p < techDesc.Passes; ++p)
    {
        mStreamOutTech->GetPassByIndex( p )->Apply(0);
        
		if( mFirstRun )
		{
			m_pLevel->GetDevice()->Draw(1, 0);
			mFirstRun = false;
		}
		else
		{
			m_pLevel->GetDevice()->DrawAuto();
		}
    }

	// done streaming-out--unbind the vertex buffer
	ID3D10Buffer* bufferArray[1] = {0};
	m_pLevel->GetDevice()->SOSetTargets(1, bufferArray, &offset);

	// ping-pong the vertex buffers
	std::swap(mDrawVB, mStreamOutVB);

	//
	// Draw the updated particle system we just streamed-out. 
	//
	m_pLevel->GetDevice()->IASetVertexBuffers(0, 1, &mDrawVB, &stride, &offset);

	mDrawTech->GetDesc( &techDesc );
    for(UINT p = 0; p < techDesc.Passes; ++p)
    {
        mDrawTech->GetPassByIndex( p )->Apply(0);
        
		m_pLevel->GetDevice()->DrawAuto();
    }
}

void ParticleSystem::BuildVertexBuffer()
{
	//
	// Create the buffer to kick-off the particle system.
	//

    D3D10_BUFFER_DESC vbd;
    vbd.Usage = D3D10_USAGE_DEFAULT;
    vbd.ByteWidth = sizeof(ParticleVertex) * 1;
    vbd.BindFlags = D3D10_BIND_VERTEX_BUFFER;
    vbd.CPUAccessFlags = 0;
    vbd.MiscFlags = 0;

	// The initial particle emitter has type 0 and age 0.  The rest
	// of the particle attributes do not apply to an emitter.
	ParticleVertex p;
	ZeroMemory(&p, sizeof(ParticleVertex));
	p.age  = 0.0f;
	p.type = 0; 
 
    D3D10_SUBRESOURCE_DATA vinitData;
    vinitData.pSysMem = &p;

	HR(m_pLevel->GetDevice()->CreateBuffer(&vbd, &vinitData, &mInitVB));
	
	//
	// Create the ping-pong buffers for stream-out and drawing.
	//
	vbd.ByteWidth = sizeof(ParticleVertex) * mMaxParticles;
    vbd.BindFlags = D3D10_BIND_VERTEX_BUFFER | D3D10_BIND_STREAM_OUTPUT;

    HR(m_pLevel->GetDevice()->CreateBuffer(&vbd, 0, &mDrawVB));
	HR(m_pLevel->GetDevice()->CreateBuffer(&vbd, 0, &mStreamOutVB));
}

void ParticleSystem::DefineInputlayout()
{
	D3D10_INPUT_ELEMENT_DESC particleDesc[] =
	{
		{"POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0,  0, D3D10_INPUT_PER_VERTEX_DATA, 0},
		{"VELOCITY", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 12, D3D10_INPUT_PER_VERTEX_DATA, 0},
		{"SIZE",     0, DXGI_FORMAT_R32G32_FLOAT,    0, 24, D3D10_INPUT_PER_VERTEX_DATA, 0},
		{"AGE",      0, DXGI_FORMAT_R32_FLOAT,       0, 32, D3D10_INPUT_PER_VERTEX_DATA, 0},
		{"TYPE",     0, DXGI_FORMAT_R32_UINT,        0, 36, D3D10_INPUT_PER_VERTEX_DATA, 0},
	};
	D3D10_PASS_DESC PassDesc;
	// Get the pass decriptor from the effect technique
	mStreamOutTech->GetPassByIndex(0)->GetDesc(&PassDesc);

    HR(m_pLevel->GetDevice()->CreateInputLayout(particleDesc, 5, PassDesc.pIAInputSignature,
		PassDesc.IAInputSignatureSize, &m_pVertexLayout));
}

void ParticleSystem::CreateEffect(ContentManager *pContentManager)
{
	if(mType == Fire)
	{
		mDefaultEffect = pContentManager->GetEffect(m_pLevel->GetDevice(), _T("./Effect/Fire.fx"));
		
	}else if(mType == Rain)
	{
		mDefaultEffect = pContentManager->GetEffect(m_pLevel->GetDevice(), _T("./Effect/Rain.fx"));	
	}
	
	mStreamOutTech = mDefaultEffect->GetTechniqueByName("StreamOutTech");
	mDrawTech = mDefaultEffect->GetTechniqueByName("DrawTech");

	GetEffectVariables(mDefaultEffect);
}

void ParticleSystem::GetEffectVariables(ID3D10Effect* pEffect)
{
	mfxViewProjVar  = pEffect->GetVariableByName("gViewProj")->AsMatrix();
	mfxGameTimeVar  = pEffect->GetVariableByName("gGameTime")->AsScalar();
	mfxTimeStepVar  = pEffect->GetVariableByName("gTimeStep")->AsScalar();
	mfxEyePosVar    = pEffect->GetVariableByName("gEyePosW")->AsVector();
	mfxEmitPosVar   = pEffect->GetVariableByName("gEmitPosW")->AsVector();
	mfxEmitDirVar   = pEffect->GetVariableByName("gEmitDirW")->AsVector();
	mfxTexArrayVar  = pEffect->GetVariableByName("gTexArray")->AsShaderResource();
	mfxRandomTexVar = pEffect->GetVariableByName("gRandomTex")->AsShaderResource();
}